/*
 * Copyright 2022 Rich yang, 18158898020@189.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

`include "defines.v"

module id(
	input wire			rst,
	input wire[`InstAddrBus]	pc_i,
	input wire[`InstBus]		inst_i,

	// read Regfile value
	input wire[`RegBus]		reg1_data_i,
	input wire[`RegBus]		reg2_data_i,

	// forward execute temp result
	input wire			ex_wreg_i,
	input wire[`RegBus]		ex_wdata_i,
	input wire[`RegAddrBus]		ex_wd_i,

	// forward mem temp result
	input wire			mem_wreg_i,
	input wire[`RegBus]		mem_wdata_i,
	input wire[`RegAddrBus]		mem_wd_i,

	// sink to regfile info
	output reg			reg1_read_o,
	output reg			reg2_read_o,
	output reg[`RegAddrBus]		reg1_addr_o,
	output reg[`RegAddrBus]		reg2_addr_o,

	output wire[`RegBus]		inst_o,

	// sink to execute stage
	output reg[`AluOpBus]		aluop_o,
	output reg[`AluSelBus]		alusel_o,
	output reg[`RegBus]		reg1_o,
	output reg[`RegBus]		reg2_o,
	output reg[`RegBus]		branch_target_op1_o,
	output reg[`RegBus]		branch_target_op2_o,
	output reg[`RegAddrBus]		wd_o,
	output reg			wreg_o,

	output reg[`RegBus]		link_address_o,

	output wire			stallreq
);

// +===========+==============+======+======+=======+=============+========+
// | inst type |  7bit        | 5bit | 5bit | 3bit  | 5bit        |  7bit  |
// +-----------+--------------+------+------+-------+-------------+--------+
// |    R      | func7        | rs2  | rs1  | func3 |  rd         | opcode |
// +-----------+--------------+------+------+-------+-------------+--------+
// |    I      |   imm[11:0]         | rs1  | func3 |  rd         | opcode |
// +-----------+--------------+------+------+-------+-------------+--------+
// |    S      | imm[11:5]    | rs2  | rs1  | func3 | imm[4:0]    | opcode |
// +-----------+--------------+------+------+-------+-------------+--------+
// |   SB      | imm[12,10:5] | rs2  | rs1  | func3 | imm[4:1,11] | opcode |
// +-----------+--------------+------+------+-------+-------------+--------+
// |   UJ      |       imm[20,10:1,11,19:12]        |  rd         | opcode |
// +-----------+------------------------------------+-------------+--------+
// |   U       |           imm[31:12]               |  rd         | opcode |
// +-----------+------------------------------------+-------------+--------+


// first inst, ori x1, x0, 1
// 0x00106093

// second inst, and x2, x0, x1
// 0x


wire [6:0] op = inst_i[6:0];
wire [4:0] rd = inst_i[11:7];
wire [2:0] op3 = inst_i[14:12];

// immediate constant
reg[`RegBus] imm;

// Indicate Inst valid ?
reg instvalid;


wire[`RegBus] pc_plus_8;
wire[`RegBus] pc_plus_4;

assign pc_plus_8 = pc_i + 8;
assign pc_plus_4 = pc_i + 4;


assign stallreq = `NoStop;


assign inst_o = inst_i;

// stage1: decode for instruction
	always @ (*) begin
		if (rst == `RstEnable) begin
			aluop_o		<= `EXE_NOP_OP;
			alusel_o	<= `EXE_RES_NOP;
			wd_o	 	<= `NOPRegAddr;
			wreg_o		<= `WriteDisable;
			instvalid	<= `InstValid;
			reg1_read_o	<= 1'b0;
			reg2_read_o	<= 1'b0;
			reg1_addr_o	<= `NOPRegAddr;
			reg2_addr_o	<= `NOPRegAddr;
			imm		<= 32'h0;
			link_address_o	<= `ZeroWord;
			branch_target_op1_o <= `ZeroWord;
			branch_target_op2_o <= `ZeroWord;
		end else begin
			aluop_o		<= `EXE_NOP_OP;
			alusel_o	<= `EXE_RES_NOP;
			wd_o		<= rd;
			wreg_o		<= `WriteDisable;
			instvalid	<= `InstInvalid;
			reg1_read_o	<= 1'b0;
			reg2_read_o	<= 1'b0;
			reg1_addr_o	<= `NOPRegAddr;
			reg2_addr_o	<= `NOPRegAddr;
			imm		<= `ZeroWord;
			link_address_o	<= `ZeroWord;
			branch_target_op1_o <= `ZeroWord;
			branch_target_op2_o <= `ZeroWord;

			case (op)
			`INST_TYPE_I_IMM: begin
				// need write to dest register
				wreg_o		<= `WriteEnable;
				// src1 read regfile port 1
				reg1_read_o	<= 1'b1;
				reg1_addr_o	<= inst_i[19:15];
				// src2 read value from inst imm
				reg2_read_o	<= 1'b0;
				// dest register addr
				wd_o	<= rd;
				// instvalid ?
				instvalid	<= `InstInvalid;
				// imm
				imm	<= {16'h0, inst_i[31:20]};
				case (op3)
					`ANDI_FUNC3: begin
						aluop_o  <= `EXE_AND_OP;
						alusel_o <= `EXE_RES_LOGIC;
						instvalid <= `InstValid;
					end
					`ORI_FUNC3: begin
						aluop_o	 <= `EXE_OR_OP;
						alusel_o <= `EXE_RES_LOGIC;
						instvalid <= `InstValid;
					end
					`XORI_FUNC3: begin
						aluop_o  <= `EXE_XOR_OP;
						alusel_o <= `EXE_RES_LOGIC;
						instvalid <= `InstValid;
					end
					`SLLI_FUNC3: begin
						aluop_o  <= `EXE_SLL_OP;
						alusel_o <= `EXE_RES_SHIFT;
						instvalid <= `InstValid;
					end
					`SRXI_FUNC3: begin
						case(inst_i[31:25])
							`SRLI_FUNC7: begin
								aluop_o <= `EXE_SRL_OP;
								alusel_o <= `EXE_RES_SHIFT;
								instvalid <= `InstValid;
							end
							`SRAI_FUNC7: begin
								aluop_o <= `EXE_SRA_OP;
								alusel_o <= `EXE_RES_SHIFT;
								instvalid <= `InstValid;
							end
						endcase
					end
					`ADDI_FUNC3: begin
						aluop_o  <= `EXE_ADD_OP;
						alusel_o <= `EXE_RES_ARITHMETIC;
					end
					`SLTI_FUNC3: begin
						aluop_o  <= `EXE_SLT_OP;
						alusel_o <= `EXE_RES_ARITHMETIC;
					end
					`SLTIU_FUNC3: begin
						aluop_o  <= `EXE_SLTU_OP;
						alusel_o <= `EXE_RES_ARITHMETIC;
					end
				endcase
			end
			`INST_TYPE_R: begin
				// need write to dest register
				wreg_o		<= `WriteEnable;
				// src1 read regfile port 1
				reg1_read_o	<= 1'b1;
				reg1_addr_o	<= inst_i[19:15];
				// src2 read value from inst imm
				reg2_read_o	<= 1'b1;
				reg2_addr_o	<= inst_i[24:20];
				// dest register addr
				wd_o	<= rd;
				// instvalid ?
				instvalid	<= `InstInvalid;
				case (op3)
					`ANDREMU_FUNC3: begin
						case(inst_i[31:25])
							`AND_FUNC7: begin
								aluop_o	 <= `EXE_AND_OP;
								alusel_o <= `EXE_RES_LOGIC;
								instvalid <= `InstValid;
							end
							`REMU_FUNC7: begin
								aluop_o <= `EXE_REMU_OP;
								alusel_o <= `EXE_RES_DIV;
								instvalid <= `InstValid;
							end
						endcase
					end
					`ORREM_FUNC3: begin
						case(inst_i[31:25])
							`OR_FUNC7: begin
								aluop_o <= `EXE_OR_OP;
								alusel_o <= `EXE_RES_LOGIC;
								instvalid <= `InstValid;
							end
							`REM_FUNC7: begin
								aluop_o <= `EXE_REM_OP;
								alusel_o <= `EXE_RES_DIV;
								instvalid <= `InstValid;
							end
						endcase
					end
					`XORDIV_FUNC3: begin
						case(inst_i[31:25])
							`XOR_FUNC7: begin
								aluop_o <= `EXE_XOR_OP;
								alusel_o <= `EXE_RES_LOGIC;
								instvalid <= `InstValid;
							end
							`DIV_FUNC7: begin
								aluop_o <= `EXE_DIV_OP;
								alusel_o <= `EXE_RES_DIV;
								instvalid <= `InstValid;
							end
						endcase
					end
					`SLLMULH_FUNC3: begin
						case(inst_i[31:25])
							`SLL_FUNC7: begin
								aluop_o <= `EXE_SLL_OP;
								alusel_o <= `EXE_RES_SHIFT;
								instvalid <= `InstValid;
							end
							`MULH_FUNC7: begin
								aluop_o <= `EXE_MULH_OP;
								alusel_o <= `EXE_RES_MUL;
							end
						endcase
					end
					`SRXDIVU_FUNC3: begin
						case(inst_i[31:25])
							`SRL_FUNC7: begin
								aluop_o <= `EXE_SRL_OP;
								alusel_o <= `EXE_RES_SHIFT;
								instvalid <= `InstValid;
							end
							`SRA_FUNC7: begin
								aluop_o <= `EXE_SRA_OP;
								alusel_o <= `EXE_RES_SHIFT;
								instvalid <= `InstValid;
							end
							`DIVU_FUNC7: begin
								aluop_o  <= `EXE_DIVU_OP;
								alusel_o <= `EXE_RES_DIV;
								instvalid <= `InstValid;
							end
						endcase
					end
					`ADDSUBMUL_FUNC3: begin
						case(inst_i[31:25])
							`ADD_FUNC7: begin
								aluop_o  <= `EXE_ADD_OP;
								alusel_o <= `EXE_RES_ARITHMETIC;
								instvalid <= `InstValid;
							end
							`SUB_FUNC7: begin
								aluop_o  <= `EXE_SUB_OP;
								alusel_o <= `EXE_RES_ARITHMETIC;
								instvalid <= `InstValid;
							end
							`MUL_FUNC7: begin
								aluop_o <= `EXE_MUL_OP;
								alusel_o <= `EXE_RES_MUL;
							end
						endcase
					end
					`SLTMULHSU_FUNC3: begin
						case(inst_i[31:25])
							`SLT_FUNC7: begin
								aluop_o  <= `EXE_SLT_OP;
								alusel_o <= `EXE_RES_ARITHMETIC;
								instvalid <= `InstValid;
							end
							`MULHSU_FUNC7: begin
								aluop_o  <= `EXE_MULHSU_OP;
								alusel_o <= `EXE_RES_MUL;
								instvalid <= `InstValid;
							end
						endcase
					end
					`SLTUMULHU_FUNC3: begin
						case(inst_i[31:25])
							`SLTU_FUNC7: begin
								aluop_o  <= `EXE_SLTU_OP;
								alusel_o <= `EXE_RES_ARITHMETIC;
								instvalid <= `InstValid;
							end
							`MULHU_FUNC7: begin
								aluop_o  <= `EXE_MULHU_OP;
								alusel_o <= `EXE_RES_MUL;
								instvalid <= `InstValid;
							end
						endcase
					end
				endcase
			end
			`INST_TYPE_U: begin
				// need write to dest register
				wreg_o		<= `WriteEnable;
				// src1 read regfile port 1
				reg1_read_o	<= 1'b1;
				reg1_addr_o	<= 5'h0;
				// src2 read value from inst imm
				reg2_read_o	<= 1'b0;
				// imm
				imm	<= {inst_i[31:12],12'h0};

				// dest register addr
				wd_o	<= rd;

				aluop_o	 <= `EXE_OR_OP;
				alusel_o <= `EXE_RES_LOGIC;
				instvalid <= `InstValid;
			end
			// JAL
			`INST_TYPE_UJ: begin
				wreg_o		<= `WriteEnable;
				aluop_o		<= `EXE_JAL_OP;
				alusel_o	<= `EXE_RES_JUMP_BRANCH;
				reg1_read_o	<= 1'b0;
				reg2_read_o	<= 1'b0;
				wd_o		<= rd;
				link_address_o	<= pc_plus_4;
				instvalid	<= `InstValid;
				branch_target_op1_o <= pc_i;
				branch_target_op2_o <= {{12{inst_i[31]}}, inst_i[19:12], inst_i[20], inst_i[30:21], 1'b0};
			end
			`INST_JALR: begin
				wreg_o		<= `WriteEnable;
				aluop_o		<= `EXE_JALR_OP;
				alusel_o	<= `EXE_RES_JUMP_BRANCH;
				reg1_read_o	<= 1'b1;
				reg1_addr_o	<= inst_i[19:15];
				reg2_read_o	<= 1'b0;
				imm		<= `ZeroWord;

				wd_o		<= rd;
				link_address_o	<= pc_plus_4;
				instvalid	<= `InstValid;
				branch_target_op1_o <= reg1_o;
				branch_target_op2_o <= {{20{inst_i[31]}}, inst_i[30:20], 1'b0};
			end
			`INST_AUIPC: begin
				wreg_o 		<= `WriteEnable;
				aluop_o		<= `EXE_AUIPC_OP;
				alusel_o	<= `EXE_RES_ARITHMETIC;

				reg1_read_o	<= 1'b0;
				reg1_addr_o	<= `NOPRegAddr;
				reg2_read_o	<= 1'b0;
				reg2_addr_o	<= `NOPRegAddr;
				wd_o		<= rd;
				imm		<= `ZeroWord;

				instvalid	<= `InstValid;
				branch_target_op1_o <= pc_i;
				branch_target_op2_o <= {inst_i[31:12], 12'h000};
			end
			`INST_TYPE_B: begin
				wreg_o		<= `WriteDisable;
				wd_o		<= `NOPRegAddr;
				reg1_read_o	<= 1'b1;
				reg1_addr_o	<= inst_i[19:15];
				reg2_read_o	<= 1'b1;
				reg2_addr_o	<= inst_i[24:20];
				instvalid	<= `InstInvalid;
				case (op3)
					`BEQ_FUNC3: begin
						aluop_o	<= `EXE_BEQ_OP;
						alusel_o <= `EXE_RES_JUMP_BRANCH;
						instvalid <= `InstValid;
						branch_target_op1_o <= pc_i;
						branch_target_op2_o <= {{20{inst_i[31]}}, inst_i[7], inst_i[30:25], inst_i[11:8],1'b0};
					end
					`BNE_FUNC3: begin
						aluop_o	<= `EXE_BNE_OP;
						alusel_o <= `EXE_RES_JUMP_BRANCH;
						instvalid <= `InstValid;
						branch_target_op1_o <= pc_i;
						branch_target_op2_o <= {{20{inst_i[31]}}, inst_i[7], inst_i[30:25], inst_i[11:8],1'b0};

					end
					`BLT_FUNC3: begin
						aluop_o	<= `EXE_BLT_OP;
						alusel_o <= `EXE_RES_JUMP_BRANCH;
						instvalid <= `InstValid;
						branch_target_op1_o <= pc_i;
						branch_target_op2_o <= {{20{inst_i[31]}}, inst_i[7], inst_i[30:25], inst_i[11:8],1'b0};
					end
					`BGE_FUNC3: begin
						aluop_o	<= `EXE_BGE_OP;
						alusel_o <= `EXE_RES_JUMP_BRANCH;
						instvalid <= `InstValid;
						branch_target_op1_o <= pc_i;
						branch_target_op2_o <= {{20{inst_i[31]}}, inst_i[7], inst_i[30:25], inst_i[11:8], 1'b0};

					end
					`BLTU_FUNC3: begin
						aluop_o	<= `EXE_BLTU_OP;
						alusel_o <= `EXE_RES_JUMP_BRANCH;
						instvalid <= `InstValid;
						branch_target_op1_o <= pc_i;
						branch_target_op2_o <= {{20{inst_i[31]}}, inst_i[7], inst_i[30:25], inst_i[11:8], 1'b0};

					end
					`BGEU_FUNC3: begin
						aluop_o	<= `EXE_BGEU_OP;
						alusel_o <= `EXE_RES_JUMP_BRANCH;
						instvalid <= `InstValid;
						branch_target_op1_o <= pc_i;
						branch_target_op2_o <= {19'b0, inst_i[31], inst_i[7], inst_i[30:25], inst_i[11:8],1'b0};
					end
				endcase
			end
			`INST_TYPE_I_LOAD: begin
				aluop_o     <= `EXE_NOP_OP;
				alusel_o    <= `EXE_RES_NOP;
				wreg_o	    <= `WriteDisable;
				instvalid   <= `InstInvalid;
				reg1_read_o <= 1'b0;
				reg2_read_o <= 1'b0;
				reg1_addr_o <= `NOPRegAddr;
				reg2_addr_o <= `NOPRegAddr;
				imm	    <= `ZeroWord;
				link_address_o <= `ZeroWord;
				branch_target_op1_o <= `ZeroWord;
				branch_target_op2_o <= `ZeroWord;
				case (op3)
					`LB_FUNC3: begin
						aluop_o     <= `EXE_LB_OP; 
						alusel_o    <= `EXE_RES_LOAD_STORE;
						instvalid   <= `InstValid;
						wreg_o	    <= `WriteEnable;
						reg1_read_o <= 1'b1;
						reg2_read_o <= 1'b0;
						reg1_addr_o <= inst_i[19:15]; 
						wd_o	    <= rd;
					end
					`LH_FUNC3: begin
						aluop_o     <= `EXE_LH_OP;
						alusel_o    <= `EXE_RES_LOAD_STORE;
						instvalid   <= `InstValid;
						wreg_o	    <= `WriteEnable;
						reg1_read_o <= 1'b1;
						reg2_read_o <= 1'b0;
reg1_addr_o <= inst_i[19:15];
						wd_o	    <= rd;
					end
					`LW_FUNC3: begin
						aluop_o     <= `EXE_LW_OP;
						alusel_o    <= `EXE_RES_LOAD_STORE;
						instvalid   <= `InstValid;
						wreg_o	    <= `WriteEnable;
						reg1_read_o <= 1'b1;
						reg2_read_o <= 1'b0;
reg1_addr_o <= inst_i[19:15];
						wd_o	    <= rd;
					end
					`LBU_FUNC3: begin
						aluop_o     <= `EXE_LBU_OP;
						alusel_o    <= `EXE_RES_LOAD_STORE;
						instvalid   <= `InstValid;
						wreg_o	    <= `WriteEnable;
						reg1_read_o <= 1'b1;
						reg2_read_o <= 1'b0;
						reg1_addr_o <= inst_i[19:15];
						wd_o	    <= rd;
					end
					`LHU_FUNC3: begin
						aluop_o	    <= `EXE_LHU_OP;
						alusel_o    <= `EXE_RES_LOAD_STORE;
						instvalid   <= `InstValid;
						wreg_o	    <= `WriteEnable;
						reg1_read_o <= 1'b1;
						reg2_read_o <= 1'b0;
						reg1_addr_o <= inst_i[19:15];
						wd_o	    <= rd;
					end
				endcase
			end

			`INST_TYPE_S: begin
				aluop_o     <= `EXE_NOP_OP;
				alusel_o    <= `EXE_RES_NOP;
				wreg_o	    <= `WriteDisable;
				instvalid   <= `InstInvalid;
				reg1_read_o <= 1'b0;
				reg2_read_o <= 1'b0;
				reg1_addr_o <= `NOPRegAddr;
				reg2_addr_o <= `NOPRegAddr;
				imm	    <= `ZeroWord;
				link_address_o <= `ZeroWord;
				branch_target_op1_o <= `ZeroWord;
				branch_target_op2_o <= `ZeroWord;
				wd_o	    <= `NOPRegAddr;
				wreg_o	    <= `WriteDisable;
				case (op3)
					`SB_FUNC3: begin
						aluop_o     <= `EXE_SB_OP; 
						alusel_o    <= `EXE_RES_LOAD_STORE;
						instvalid   <= `InstValid;
						reg1_read_o <= 1'b1;
						reg2_read_o <= 1'b1;
						reg1_addr_o <= inst_i[19:15]; 
						reg2_addr_o <= inst_i[24:20];
					end
					`SH_FUNC3: begin
						aluop_o     <= `EXE_SH_OP;
						alusel_o    <= `EXE_RES_LOAD_STORE;
						instvalid   <= `InstValid;
						reg1_read_o <= 1'b1;
						reg2_read_o <= 1'b1;
						reg1_addr_o <= inst_i[19:15];
						reg2_addr_o <= inst_i[24:20];
					end
					`SW_FUNC3: begin
						aluop_o     <= `EXE_SW_OP;
						alusel_o    <= `EXE_RES_LOAD_STORE;
						instvalid   <= `InstValid;
						reg1_read_o <= 1'b1;
						reg2_read_o <= 1'b1;
						reg1_addr_o <= inst_i[19:15];
						reg2_addr_o <= inst_i[24:20];
					end
				endcase
			end


			default: begin

			end
			endcase

		end	// if
	end		// always

	// stage2: ensure oprand 1
	always @ (*) begin
		if (rst == `RstEnable) begin
			reg1_o <= `ZeroWord;
		end else if ((reg1_read_o == 1'b1) && (ex_wreg_i == 1'b1)
				&& (ex_wd_i == reg1_addr_o)) begin
			reg1_o <= ex_wdata_i;
		end else if ((reg1_read_o == 1'b1) && (mem_wreg_i == 1'b1)
				&& (mem_wd_i == reg1_addr_o)) begin
			reg1_o <= mem_wdata_i;
		end else if (reg1_read_o == 1'b1) begin
			reg1_o <= reg1_data_i;
		end else if (reg1_read_o == 1'b0) begin
			reg1_o <= imm;
		end else begin
			reg1_o <= `ZeroWord;
		end
	end

	// stage3: ensure oprand 2
	always @ (*) begin
		if (rst == `RstEnable) begin
			reg2_o <= `ZeroWord;
		end else if ((reg2_read_o == 1'b1) && (ex_wreg_i == 1'b1)
				&& (ex_wd_i == reg2_addr_o)) begin
			reg2_o <= ex_wdata_i;
		end else if ((reg2_read_o == 1'b1) && (mem_wreg_i == 1'b1)
				&& (mem_wd_i == reg2_addr_o)) begin
			reg2_o <= mem_wdata_i;
		end else if (reg2_read_o == 1'b1) begin
			reg2_o <= reg2_data_i;
		end else if (reg2_read_o == 1'b0) begin
			reg2_o <= imm;
		end else begin
			reg2_o <= `ZeroWord;
		end
	end

endmodule
